package googleearthdownloader;

import java.awt.event.MouseEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.awt.image.RasterFormatException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.awt.event.KeyAdapter;
import java.util.Arrays;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.ArrayBlockingQueue;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JWindow;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.WindowConstants;
import javax.imageio.ImageIO;
import javax.swing.Timer;

import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngReader;
import ar.com.hjg.pngj.PngWriter;

public class MapViewerSystem {

	private JFrame jf;
	private BufferedImage mainImage;
	private BufferedImage blackImage;
	// private BufferedImage[][] usedImages;
	// private SubImage[][] si;
	private JScrollPane sp;
	private JDialog statusFrame;
	private JPanel statusPanel, jp, inputPanel, fullScreenPanel;
	private JLabel statusText, xStartLabel, xEndLabel, yStartLabel, yEndLabel,
			zHeightLabel;
	private ScrollableImage mapLabel;
	private JTextField xStartTextField, xEndTextField, yStartTextField,
			yEndTextField, zHeightTextField;
	private JProgressBar statusDownloadProgress;
	private int statusDownloadProgressNumber = 0;
	private JButton cancelButton, showButton, saveButton;
	private SwingWorker<Void, SubImage> viewWorker;
	private SwingWorker<Void, Void> saveWorker;
	private Timer loadFinishedTimer;
	private ActionListener loadFinishedAction, showButtonAction,
			saveButtonAction, cancleButtonAction;
	private int screenHeight, screenWidth, xstart, ystart, xend, yend, z,
			xsize, ysize;
	private File pictureObjectDirectory, saveDirectory;
	private JWindow fullScreenWindow;
	private MouseAdapter mouseAdapter;
	private MouseMotionAdapter mouseMotionAdapter;
	private boolean fullScreenMode;
	private KeyListener fullScreenToggler;
	private int deltaMouseX, deltaMouseY, mouseOldX, mouseOldY,
			currentViewportX, currentViewportY;
	private SystemLog systemLog;

	private ArrayBlockingQueue<int[]> myQueue;

	public MapViewerSystem(SystemLog systemLog) {

		this.systemLog = systemLog;

		myQueue = new ArrayBlockingQueue<int[]>(8);

		saveDirectory = new File(new File("").getAbsolutePath()
				+ File.separator+"savedData"+ File.separator+"UserCreatedPictures");
		if (!saveDirectory.exists()) {
			saveDirectory.mkdirs();
		}

		initUI();
	}

	public JFrame getMainFrame() {
		return jf;
	}

	private void initUI() {
		try {
			SwingUtilities.invokeAndWait(new Runnable() {
				public void run() {
					screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
					screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;

					jf = new JFrame("BufferedImageTest");
					jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
					jf.setFocusable(true);
					jp = new JPanel(new BorderLayout(), true);
					inputPanel = new JPanel(new GridLayout(2, 6));

					statusFrame = new JDialog(jf, "ScreenLoadStatus", false);
					statusFrame
							.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
					statusFrame.setResizable(false);
					statusPanel = new JPanel(new BorderLayout(), true);
					statusText = new JLabel("no loading jobs");
					statusDownloadProgress = new JProgressBar();
					statusDownloadProgress.setPreferredSize(new Dimension(
							screenWidth * 5 / 28, screenHeight / 21));
					statusDownloadProgress.setMinimum(0);
					statusDownloadProgress.setMaximum(100);

					cancelButton = new JButton("cancel");
					cancelButton.setEnabled(false);
					showButton = new JButton("show");
					saveButton = new JButton("save");
					saveButton.setEnabled(true);

					xStartLabel = new JLabel("xStart");
					xEndLabel = new JLabel("xEnd");
					yStartLabel = new JLabel("yStart");
					yEndLabel = new JLabel("yEnd");
					zHeightLabel = new JLabel("zHeight");
					mapLabel = new ScrollableImage();
					mapLabel.setHorizontalAlignment(SwingConstants.CENTER);
					mapLabel.setVerticalAlignment(SwingConstants.CENTER);

					xStartLabel.setHorizontalAlignment(SwingConstants.CENTER);
					xEndLabel.setHorizontalAlignment(SwingConstants.CENTER);
					yStartLabel.setHorizontalAlignment(SwingConstants.CENTER);
					yEndLabel.setHorizontalAlignment(SwingConstants.CENTER);
					zHeightLabel.setHorizontalAlignment(SwingConstants.CENTER);

					xStartLabel.setBorder(BorderFactory
							.createLineBorder(Color.black));
					xEndLabel.setBorder(BorderFactory
							.createLineBorder(Color.black));
					yStartLabel.setBorder(BorderFactory
							.createLineBorder(Color.black));
					yEndLabel.setBorder(BorderFactory
							.createLineBorder(Color.black));
					zHeightLabel.setBorder(BorderFactory
							.createLineBorder(Color.black));

					xStartTextField = new JTextField();
					xEndTextField = new JTextField();
					yStartTextField = new JTextField();
					yEndTextField = new JTextField();
					zHeightTextField = new JTextField();

					xStartTextField
							.setHorizontalAlignment(SwingConstants.CENTER);
					xEndTextField.setHorizontalAlignment(SwingConstants.CENTER);
					yStartTextField
							.setHorizontalAlignment(SwingConstants.CENTER);
					yEndTextField.setHorizontalAlignment(SwingConstants.CENTER);
					zHeightTextField
							.setHorizontalAlignment(SwingConstants.CENTER);

					xStartTextField.setBorder(BorderFactory
							.createLineBorder(Color.black));
					xEndTextField.setBorder(BorderFactory
							.createLineBorder(Color.black));
					yStartTextField.setBorder(BorderFactory
							.createLineBorder(Color.black));
					yEndTextField.setBorder(BorderFactory
							.createLineBorder(Color.black));
					zHeightTextField.setBorder(BorderFactory
							.createLineBorder(Color.black));

					inputPanel.add(xStartLabel);
					inputPanel.add(xEndLabel);
					inputPanel.add(yStartLabel);
					inputPanel.add(yEndLabel);
					inputPanel.add(zHeightLabel);
					inputPanel.add(showButton);
					inputPanel.add(xStartTextField);
					inputPanel.add(xEndTextField);
					inputPanel.add(yStartTextField);
					inputPanel.add(yEndTextField);
					inputPanel.add(zHeightTextField);
					inputPanel.add(saveButton);

					statusPanel.add(cancelButton, BorderLayout.EAST);
					statusPanel.add(statusText, BorderLayout.WEST);
					statusFrame.add(statusDownloadProgress, BorderLayout.NORTH);
					statusFrame.add(statusPanel, BorderLayout.SOUTH);
					statusFrame.pack();

					sp = new JScrollPane(mapLabel,
							ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
							ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
					// sp = new
					// JScrollPane(mapLabel,ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

					jp.setPreferredSize(new Dimension(screenWidth * 10 / 21,
							screenHeight * 4 / 7));
					jp.add(sp, BorderLayout.CENTER);

					jf.setResizable(false);
					jf.add(jp, BorderLayout.NORTH);
					jf.add(inputPanel, BorderLayout.SOUTH);
					jf.pack();

					// FullScreenStuff
					fullScreenWindow = new JWindow();
					fullScreenPanel = new JPanel(new BorderLayout(), true);
					fullScreenPanel.setPreferredSize(new Dimension(screenWidth,
							screenHeight));
					fullScreenWindow.add(fullScreenPanel);
					fullScreenWindow.pack();
					fullScreenMode = false;

					// Positioning
					jf.setLocation((screenWidth - jf.getSize().width) / 2,
							(screenHeight - jf.getSize().height) / 2);
					statusFrame.setLocation(jf.getLocation().x + jf.getWidth()
							+ 18, jf.getLocation().y);

					// Listeners

					fullScreenToggler = new KeyAdapter() {
						public void keyReleased(KeyEvent e) {

							if (e.getKeyCode() == KeyEvent.VK_F1) {
								if (fullScreenMode) {
									fullScreenMode = false;
									jp.add(sp);

									jf.requestFocusInWindow();

									jp.updateUI();

								} else {
									fullScreenMode = true;
									fullScreenPanel.add(sp);

									jf.requestFocusInWindow();
									mapLabel.requestFocusInWindow();

								}
								fullScreenWindow.setVisible(fullScreenMode);
							}

						}
					};
					jf.addKeyListener(fullScreenToggler);

					loadFinishedAction = new ActionListener() {

						@Override
						public void actionPerformed(ActionEvent arg0) {
							statusText
									.setText("press F1 to toggle fullscreen view");
							statusDownloadProgress.setIndeterminate(false);
							statusDownloadProgress.setValue(0);
							jf.requestFocusInWindow();
							loadFinishedTimer.stop();
						}

					};
					loadFinishedTimer = new Timer(2000, loadFinishedAction);

					showButtonAction = new ActionListener() {

						@Override
						public void actionPerformed(ActionEvent e) {

							try {
								int n1, n2, n3, n4, n5;
								n1 = new Integer(xStartTextField.getText())
										.intValue();
								n2 = new Integer(xEndTextField.getText())
										.intValue();
								n3 = new Integer(yStartTextField.getText())
										.intValue();
								n4 = new Integer(yEndTextField.getText())
										.intValue();
								n5 = new Integer(zHeightTextField.getText())
										.intValue();

								try {
									setView(n1, n2, n3, n4, n5);
									showButton.setEnabled(false);
									xStartTextField.setEditable(false);
									xEndTextField.setEditable(false);
									yStartTextField.setEditable(false);
									yEndTextField.setEditable(false);
									zHeightTextField.setEditable(false);
									cancelButton.setEnabled(true);
								} catch (IllegalArgumentException iae) {
									iae.printStackTrace();
									// JOptionPane.showMessageDialog(jf,
									// "your endvalues are bigger than your startvalues",
									// "Invalid input",
									// JOptionPane.ERROR_MESSAGE);
								}
							} catch (NumberFormatException nfe) {
								JOptionPane.showMessageDialog(jf,
										"your input has to be numbers",
										"Invalid input",
										JOptionPane.ERROR_MESSAGE);
							}
						}

					};
					showButton.addActionListener(showButtonAction);

					cancleButtonAction = new ActionListener() {

						@Override
						public void actionPerformed(ActionEvent arg0) {
							if (!viewWorker.isCancelled()) {
								viewWorker.cancel(true);
								statusDownloadProgress.setIndeterminate(true);
								loadFinishedTimer.start();
							}
						}

					};
					cancelButton.addActionListener(cancleButtonAction);

					saveButtonAction = new ActionListener() {

						@Override
						public void actionPerformed(ActionEvent arg0) {
							savePicture();
						}
					};
					saveButton.addActionListener(saveButtonAction);

					statusFrame.setVisible(true);
					jf.setVisible(true);
				}
			});
		} catch (InterruptedException e) {
			// TODO
			// systemLog.log("InterruptedException in MapViewerSystem GUI creation",
			// SystemLog.SHORT_ERROR_MESSAGE);
			e.printStackTrace();
		} catch (InvocationTargetException e) {

			// TODO
			// systemLog.log("InvocationTargetException in MapViewerSystem GUI creation",
			// SystemLog.SHORT_ERROR_MESSAGE);
			e.printStackTrace();
		}

	}

	private synchronized void savePicture() {

		showButton.setEnabled(false);
		saveButton.setEnabled(false);
		statusDownloadProgress.setIndeterminate(true);
		statusText.setText("saving image");

		int n1, n2, n3, n4, n5;
		n1 = new Integer(xStartTextField.getText()).intValue();
		n2 = new Integer(xEndTextField.getText()).intValue();
		n3 = new Integer(yStartTextField.getText()).intValue();
		n4 = new Integer(yEndTextField.getText()).intValue();
		n5 = new Integer(zHeightTextField.getText()).intValue();

		final int yImagesStart = n3;
		final int yImagesEnd = n4;
		final int xImagesStart = n1;
		final int xImagesEnd = n2;
		final int z = n5;

		final int xPixels = 256;
		final int yPixels = 256;
		final int colorDepth = 8;
		final boolean alpha = false;

		new Thread() {

			@Override
			public void run() {

				String blackImage = new File("").getAbsolutePath()+ File.separator+"black.png";
				File inputDirectory = new File(new File("").getAbsolutePath()+ File.separator+"savedData"+ File.separator+"height " + z + File.separator);
				int numberOfIntegersPerInImageRow = xPixels * 3; // TODO maybe not always 3
				int numberOfImagesPerRow = (xImagesEnd - xImagesStart + 1);
				TreeSet<String> filesAlreadyOnDisk = new TreeSet<String>(Arrays.asList(inputDirectory.list()));
				String[] filenames = new String[numberOfImagesPerRow];
				PngReader[] readers = new PngReader[numberOfImagesPerRow];
				int[] scanLineIn;

				for (int yImageCounter = yImagesStart; yImageCounter <= yImagesEnd; yImageCounter++) {
					int q = 0;

					for (int x = xImagesStart; x <= xImagesEnd; x++) {

						// if
						// (filesAlreadyOnDisk.remove("x="+x+" y="+yImageCounter+" z="+z+".jpg")){
						// filenames[q] =
						// inputDirectory.getAbsolutePath()+File.separator+"x="+x+" y="+yImageCounter+" z="+z+".jpg";
						if (filesAlreadyOnDisk.remove("x=" + x + " y="+ yImageCounter + " z=" + z + ".png")) {
							filenames[q] = inputDirectory.getAbsolutePath()+ File.separator + "x=" + x + " y="+ yImageCounter + " z=" + z + ".png";
						} else {
							filenames[q] = blackImage;
						}
						q++;
					}

					for (int i = 0; i < numberOfImagesPerRow; i++) {
						readers[i] = new PngReader(new File(filenames[i]));
					}

					for (int yPixelCounter = 0; yPixelCounter < yPixels; yPixelCounter++) {

						int[] composedLine = new int[numberOfIntegersPerInImageRow* numberOfImagesPerRow];

						q = 0;
						for (int i = 0; i < numberOfImagesPerRow; i++) {

							ImageLineInt readLine = (ImageLineInt) readers[i]
									.readRow();
							scanLineIn = readLine.getScanline();

							for (int w = 0; w < numberOfIntegersPerInImageRow; w++) {
								composedLine[q] = scanLineIn[w];
								q++;
							}
						}

						try {
							myQueue.put(composedLine);
						} catch (InterruptedException e) {
							// TODO Automatisch generierter Erfassungsblock
							e.printStackTrace();
						}
					}

					for (int i = 0; i < numberOfImagesPerRow; i++) {
						readers[i].end();
					}

				}
			}
		}.start();

		new Thread() {

			@Override
			public void run() {

				File outputFile = new File(saveDirectory.getAbsolutePath()+ File.separator+"x=" + xstart + "-" + xend + "y=" + ystart + "-"+ yend + "z=" + z + ".png");

				ImageInfo imageInfoRow = new ImageInfo((xImagesEnd- xImagesStart + 1)* xPixels, 1, colorDepth, alpha);
				ImageInfo imageInfoOutput = new ImageInfo((xImagesEnd- xImagesStart + 1)* xPixels, (yImagesEnd - yImagesStart + 1) * yPixels,colorDepth, alpha);
				PngWriter writer = new PngWriter(outputFile, imageInfoOutput);
				int numerOfLines = yPixels * (yImagesEnd - yImagesStart + 1);
				
				
				
				for (int i = 0; i < numerOfLines; i++) {
					try {
						writer.writeRow(new ImageLineInt(imageInfoRow, myQueue.take()));
						System.out.println(i+1 +" of "+numerOfLines);
					} catch (InterruptedException e) {
						// TODO Automatisch generierter Erfassungsblock
						e.printStackTrace();
					}
				}

				writer.end();
				
				
				SwingUtilities.invokeLater(new Runnable(){

					@Override
					public void run() {
						JOptionPane
						.showMessageDialog(jf, "Your image is saved to:\n"
								+ saveDirectory.getAbsolutePath() + "\\x="
								+ xstart + "-" + xend + "y=" + ystart + "-"
								+ yend + "z=" + z + ".png", "File saved",
								JOptionPane.INFORMATION_MESSAGE);
				showButton.setEnabled(true);
				saveButton.setEnabled(true);
				xStartTextField.setEditable(true);
				xEndTextField.setEditable(true);
				yStartTextField.setEditable(true);
				yEndTextField.setEditable(true);
				zHeightTextField.setEditable(true);
				loadFinishedTimer.start();
					}
				});

			}
		}.start();

	}

	private synchronized void setView(int x, int xe, int y, int ye, int zheight) {

		xstart = x;
		ystart = y;
		xend = xe;
		yend = ye;
		z = zheight;
		xsize = xe - x + 1;
		ysize = ye - y + 1;
		statusDownloadProgressNumber = 0;

		// mainImage = null;
		// pictureObjectDirectory=null;
		// usedImages= null;
		// si=null;
		// viewWorker= null;

		pictureObjectDirectory = new File(new File("").getAbsolutePath()+ File.separator+"savedData"+ File.separator+"height " + z);

		// mainImage = new
		// BufferedImage(xsize*256,ysize*256,BufferedImage.TYPE_INT_ARGB);
		mainImage = new BufferedImage(xsize * 256, ysize * 256,
				BufferedImage.TYPE_3BYTE_BGR);

		mapLabel.setIcon(new ImageIcon(mainImage));
		// usedImages = new BufferedImage[xsize][ysize];
		// si = new SubImage[xsize][ysize];

		viewWorker = new SwingWorker<Void, SubImage>() {

			@Override
			protected Void doInBackground() throws Exception {

				blackImage = ImageIO.read(new File((new File("").getAbsolutePath()) +File.separator+ "black.png"));

				for (int i = 0; (i <= xend - xstart) && (!isCancelled()); i++) {

					for (int q = 0; (q <= yend - ystart) && (!isCancelled()); q++) {

						File currentFile = new File(
								pictureObjectDirectory.getAbsolutePath()+File.separator+"x=" + (i + xstart) + " y="+ (q + ystart) + " z=" + z + ".png");

						
						System.out.println(currentFile.getAbsoluteFile());
						
						if (currentFile.exists()) {
							try {
						

								publish(new SubImage(i * 256, q * 256, ImageIO
										.read(currentFile).getSubimage(0, 0,
												256, 256)));

							} catch (RasterFormatException e) {
								System.out.println("RasterFormatException at "
										+ currentFile.getAbsolutePath());
								
							} catch (IOException e) {
								e.printStackTrace();
							}
						} else {
							

							publish(new SubImage(i * 256, q * 256,
									blackImage.getSubimage(0, 0, 256, 256)));

							
						}

						statusDownloadProgressNumber++;
						setProgress(statusDownloadProgressNumber * 100
								/ (xsize * ysize));
					}
				}

				return null;
			}

			@Override
			protected void process(List<SubImage> chunks) {
				for (int i = 0; i < chunks.size(); i++) {
					mainImage.getGraphics().drawImage(chunks.get(i).getImage(),
							chunks.get(i).getX(), chunks.get(i).getY(), jp);
				}
				// jp.updateUI();
			}

			@Override
			protected void done() {
				cancelButton.setEnabled(false);
				showButton.setEnabled(true);
				xStartTextField.setEditable(true);
				xEndTextField.setEditable(true);
				yStartTextField.setEditable(true);
				yEndTextField.setEditable(true);
				zHeightTextField.setEditable(true);
				loadFinishedTimer.start();
			}
		};

		viewWorker.addPropertyChangeListener(new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent pce) {
				if (pce.getPropertyName().equals("progress")) {
					statusDownloadProgress.setValue(viewWorker.getProgress());
					statusText.setText("Status: "
							+ statusDownloadProgressNumber + " of "
							+ (xsize * ysize) + " loaded");
					statusPanel.updateUI();
				}
			}

		});

		cancelButton.setEnabled(true);

		viewWorker.execute();
	}
}
